#ifndef PRINT_H
#define PRINT_H

#include <limits.h>
#include <iostream>
#include <errno.h>
#include <cassert>
#include <stdarg.h>
#include <vector>
#include <algorithm>

#include "print_helper.h"

class DataBuffer
{
	public: 
		void write_char(char ch, int & written) 
		{
			if (ch == '\0')
			{
				written = -1;
			}
			else
			{
				written ++;
			}
			m_data.push_back(ch);			
		}
		~DataBuffer()
		{
			std::copy(m_data.begin(),m_data.end(),std::ostream_iterator<char>(std::cout)); 
		}
		void write_multi_char(char ch, int num, int & written)
		{
			while (num-- > 0) {
				write_char(ch, written);
				if (num == -1)
					break;
			}
		}

		void write_string(char *str, int len, int & written)
		{
			while (len-- > 0) {
				write_char(*str++, written);
				if (written == -1)
				{
					if (errno == EILSEQ)
						write_char('?', written);
					else
						break;
				}
			}
		}
private:
	std::vector<char> m_data; 
};

//read arg from va_list 
template <class T>
T get_arg(va_list* pargptr)
{
	return va_arg(*pargptr,T);
};

//read point arg 
template<>
void * get_arg<void *>(va_list* pargptr)
{
	return (void*)get_arg<int>(pargptr);
};

#ifndef WIN32 
template<>
short get_arg<short>(va_list* pargptr)
{
	return (short)get_arg<int >(pargptr);
};
template<>
wchar_t get_arg<wchar_t>(va_list* pargptr)
{
	return (short)get_arg<int>(pargptr);
};
#endif // 

template <class T>
class DataAction
{
public:
    DataAction(DataBuffer &st):store(st)
    {
    }
    void operator()(char ) const
    {
    }
    void operator()(const char * beg, const char * end) const
    {
    }
private:
    DataBuffer & store;
};

struct NonFmtType
{
};



/*
 * Buffer size required to be passed to _gcvt, fcvt and other fp conversion routines
 * */
#define _CVTBUFSIZE (309+40) /* # of digits in max. dp value + slop */

#define BUFFERSIZE    512
#define MAXPRECISION  BUFFERSIZE


template <class DataBuffer> 
int print(DataBuffer& obuf, const char * format, va_list & pargs)
{
	int hexadd=0;     /* offset to add to number to get 'a'..'f' */
	char ch; 
	int charsout =0;        /* characters currently written so far, -1 = IO error */
	enum CHARSTATE chstate =ST_NORMAL;   /* current state */
	enum CHARTYPE chclass; /* class of current character */
	int radix;      /* current conversion radix */
	int flags=0;        /* flag word -- see #defines above for flag values */

	int prefixlen=0;    /* length of prefix -- 0 means no prefix */
	int fldwidth = 0;   /* selected field width -- 0 means default */
	int capexp;         /* non-zero = 'E' exponent signifient, zero = 'e' */
	bool no_output=false;    /* non-zero = prodcue no output for this specifier */
	int precision = 0;  /* selected precision  -- -1 means default */

	char prefix[2];    /* numeric prefix -- up to two characters */

	wchar_t wchar;                      /* temp wchar_t */
	union {
		char *sz;   /* pointer text to be printed, not zero terminated */
		wchar_t *wz;
	} text;

	int textlen=0;    /* length of the text in bytes/wchars to be printed.
					   textlen is in multibyte or wide chars if _UNICODE */
	union {
		char sz[BUFFERSIZE];
		//#ifdef _UNICODE
		//		wchar_t wz[BUFFERSIZE];
		//#endif  /* _UNICODE */
	} buffer;
	int buffersize=0;                     /* size of text.sz (used only for the call to _cfltcvt) */
	bool bufferiswide=false;         /* non-zero = buffer contains wide chars already */

	char *heapbuf = NULL; /* non-zero = test.sz using heap buffer to be freed */

	while((ch = * format++ )!= '\0'&& charsout >=0)
	{
		chclass = FIND_CHAR_CLASS(__lookuptable, ch);  /* find character class */
		chstate = FIND_NEXT_STATE(__lookuptable, chclass, chstate); /* find next state */

		switch(chstate)
		{
			case ST_NORMAL:
				{
NORMAL_STATE:
					obuf.write_char(ch,charsout);
				}
				break; 
			case ST_PERCENT:
				{
					prefixlen = fldwidth = capexp = 0; 
					no_output  = false; 
					flags = 0; 
					precision = -1; 
					bufferiswide = false; 
				}
				break; 
			case ST_FLAG:
				{
					/* set flag based on which flag character */
					switch (ch)
					{
						case '-':
							flags |= FL_LEFT;   /* '-' => left justify */
							break;
						case '+':
							flags |= FL_SIGN;   /* '+' => force sign indicator */
							break;
						case ' ':
							flags |= FL_SIGNSP; /* ' ' => force sign or space */
							break;
						case '#':
							flags |= FL_ALTERNATE;  /* '#' => alternate form */
							break;
						case '0':
							flags |= FL_LEADZERO;   /* '0' => pad with leading zeros */
							break;
					}
				}
				break ;
			case ST_WIDTH:
				{
					/* update width value */
					if (ch == '*')
					{
						/* get width from arg list */
						fldwidth = get_arg<int>(&pargs);
						if (fldwidth < 0)
						{
							/* ANSI says neg fld width means '-' flag and pos width */
							flags |= FL_LEFT;
							fldwidth = -fldwidth;
						}
					}
					else
					{
						/* add digit to current field width */
						fldwidth = fldwidth * 10 + (ch - '0');
					}
				}
				break; 
			case ST_DOT:
				{
					/* zero the precision, since dot with no number means 0
					 *                        not default, according to ANSI */
					precision = 0;
				}
				break;


			case ST_PRECIS:
				{
					/* update precison value */
					if (ch == '*') {
						/* get precision from arg list */
						precision = get_arg<int>(&pargs);
						if (precision < 0) {
							precision = -1; /* neg precision means default */
						}
					}
					else {
						/* add digit to current precision */
						precision = precision * 10 + (ch - '0');
					}

				}
				break;

			case ST_SIZE:
				{
					/* just read a size specifier, set the flags based on it */
					switch(ch)
					{

						case 'l':
							/*In order to handle the ll case, we depart from the
							 * simple deterministic state machine.      */
							if (*format == 'l') {
								++format;
								flags |= FL_LONGLONG;   /* 'll' => long long */
							}
							else {
								flags |= FL_LONG;   /* 'l' => long int or wchar_t */
							}
							break;


						case 'I':
							/*
							 * In order to handle the I, I32, and I64 size modifiers, we
							 * depart from the simple deterministic state machine. The
							 * code below scans for characters following the 'I',
							 * and defaults to 64 bit on WIN64 and 32 bit on WIN32
							 */
#if PTR_IS_INT64
							flags |= FL_I64;    /* 'I' => __int64 on WIN64 systems */
#endif  /* PTR_IS_INT64 */
							if ( (*format == '6') && (*(format + 1) == '4') )
							{
								format += 2;
								flags |= FL_I64;    /* I64 => __int64 */
							}
							else if ( (*format == '3') && (*(format + 1) == '2') )
							{
								format += 2;
								flags &= ~FL_I64;   /* I32 => __int32 */
							}
							else if ( (*format == 'd') ||
									(*format == 'i') ||
									(*format == 'o') ||
									(*format == 'u') ||
									(*format == 'x') ||
									(*format == 'X') )
							{
								/*
								 * Nothing further needed.  %Id (et al) is
								 * handled just like %d, except that it defaults to 64 bits
								 * on WIN64.  Fall through to the next iteration.
								 */
							}
							else
							{
								chstate = ST_NORMAL;
								goto NORMAL_STATE;
							}

					}
					break; 
					case ST_TYPE:
					{
						/* we have finally read the actual type character, so we       */
						/* now format and "print" the output.  We use a big switch     */
						/* statement that sets 'text' to point to the text that should */
						/* be printed, and 'textlen' to the length of this text.       */
						/* Common code later on takes care of justifying it and        */
						/* other miscellaneous chores.  Note that cases share code,    */
						/* in particular, all integer formatting is done in one place. */
						/* Look at those funky goto statements!                        */

						switch(ch)
						{
							case 'C': /* ISO wide character */
								{
									if (!(flags & (FL_SHORT|FL_LONG|FL_WIDECHAR)))
									{
										flags |= FL_WIDECHAR;   /* ISO std. */
									}
									/* fall into 'c' case */
								}
							case 'c':
								{
									/* print a single character specified by int argument */
									if (flags & (FL_LONG|FL_WIDECHAR))
									{
										//TODO
										wchar = get_arg<wchar_t>(&pargs);
#ifdef WIN32
										/* convert to multibyte character */
										errno_t e = wctomb_s(&textlen, buffer.sz, sizeof(buffer.sz), wchar);
										///* check that conversion was successful */
										if (e != 0)
											no_output = true;
#else 
										int bytes= wctomb(buffer.sz,wchar);
										if (bytes >0)
											no_output = true; 
#endif //
									} else {
										/* format multibyte character */
										/* this is an extension of ANSI */
										unsigned short temp = (unsigned short)get_arg<int>(&pargs);
										{
											buffer.sz[0] = (char) temp;
											textlen = 1;
										}
									}
									text.sz = buffer.sz;
								}
								break;
							case 'Z':
								{
									/* print a Counted String
									   int i;
									   char *p;       // temps */
									struct _count_string
									{
										short Length;
										short MaximumLength;
										char *Buffer;
									} *pstr;

									pstr = (struct _count_string *)get_arg<void*>(&pargs);

									if (pstr == NULL || pstr->Buffer == NULL) {
										/* null ptr passed, use special string */
										text.sz = __nullstring;
										textlen = (int)strlen(text.sz);
									} else
									{
										if (flags & FL_WIDECHAR) {
											text.wz = (wchar_t *)pstr->Buffer;
											textlen = pstr->Length / (int)sizeof(wchar_t);
											bufferiswide = true;
										} else
										{
											bufferiswide = false;
											text.sz = pstr->Buffer;
											textlen = pstr->Length;
										}
									}

								}
								break;
							case 'S':/* ISO wide character string */
								{
									if (!(flags & (FL_SHORT|FL_LONG|FL_WIDECHAR)))
										flags |= FL_SHORT;
								}

							case 's':
								{
									/* print a string --                            */
									/* ANSI rules on how much of string to print:   */
									/*   all if precision is default,               */
									/*   min(precision, length) if precision given. */
									/* prints '(null)' if a null string is passed   */
																		
									
									/* At this point it is tempting to use strlen(), but */
									/* if a precision is specified, we're not allowed to */
									/* scan past there, because there might be no null   */
									/* at all.  Thus, we must do our own scan.           */

									int i = (precision == -1) ? INT_MAX : precision;
									text.sz = (char *)get_arg<void*>(&pargs);
									/* scan for null upto i characters */
									if (flags & (FL_LONG|FL_WIDECHAR))
									{
										if (text.wz == NULL) /* NULL passed, use special string */
											text.wz = __wnullstring;
										bufferiswide = true;
										wchar_t * pwch = text.wz;
										while ( i-- && *pwch )
											++pwch;
										textlen = (int)(pwch - text.wz);
										/* textlen now contains length in wide chars */
									} else
									{
										if (text.sz == NULL) /* NULL passed, use special string */
											text.sz = __nullstring;
										char *p = text.sz;
										while (i-- && *p)
											++p;
										textlen = (int)(p - text.sz);    /* length of the string */
									}

								}
								break;
							case 'n':
								{
									/* write count of characters seen so far into */
									/* short/int/long thru ptr read from args */
									void *p = get_arg<void*>(&pargs);

									/* if %n is disabled, we skip an arg and print 'n' */
									//if ( !_get_printf_count_output() )
									//{
									//  _VALIDATE_RETURN(("'n' format specifier disabled", 0), EINVAL, -1);
									//  break;
									//}

									/* store chars out into short/long/int depending on flags */
#if !LONG_IS_INT
									if (flags & FL_LONG)
										*(long *)p = charsout;
									else
#endif  /* !LONG_IS_INT */

#if !SHORT_IS_INT
										if (flags & FL_SHORT)
											*(short *)p = (short) charsout;
										else
#endif  /* !SHORT_IS_INT */
											*(int *)p = charsout;

									no_output = true;              /* force no output */
								}
								break;

							case 'E':
							case 'G':
							case 'A':
								{
									capexp = 1;                 /* capitalize exponent */
									ch += 'a' - 'A';    /* convert format char to lower */
									/* DROP THROUGH */
								}
							case 'e':
							case 'f':
							case 'g':
							case 'a':
								{
									/* floating point conversion -- we call cfltcvt routines */
									/* to do the work for us.                                */
									flags |= FL_SIGNED;         /* floating point is signed conversion */
									text.sz = buffer.sz;        /* put result in buffer */
									buffersize = BUFFERSIZE;

									/* compute the precision value */
									if (precision < 0)
										precision = 6;          /* default precision: 6 */
									else if (precision == 0 && ch == 'g')
										precision = 1;          /* ANSI specified */
									else if (precision > MAXPRECISION)
										precision = MAXPRECISION;

									if (precision > BUFFERSIZE - _CVTBUFSIZE) {
										/* conversion will potentially overflow local buffer */
										/* so we need to use a heap-allocated buffer.        */
										heapbuf = (char *)malloc(_CVTBUFSIZE + precision);
										if (heapbuf != NULL)
										{
											text.sz = heapbuf;
											buffersize = _CVTBUFSIZE + precision;
										}
										else
											/* malloc failed, cap precision further */
											precision = BUFFERSIZE - _CVTBUFSIZE;
									}
#if !LONGDOUBLE_IS_DOUBLE
									/* do the conversion */
									if (flags & FL_LONGDOUBLE) {
										_LONGDOUBLE tmp = get_arg<_LONGDOUBLE>(pargs);

										/* Note: assumes ch is in ASCII range */
										_CLDCVT(&tmp, text.sz, buffersize, (char)ch, precision, capexp);
									} else
#endif  /* !LONGDOUBLE_IS_DOUBLE */

									{
										double tmp=get_arg<double>(&pargs);

										/* Note: assumes ch is in ASCII range */
										/* In safecrt, we provide a special version of _cfltcvt which internally calls printf (see safecrt_output_s.c) */

										//TODO _cfltcvt_l(&tmp.x, text.sz, buffersize, (char)ch, precision, capexp, _loc_update.GetLocaleT());
									}

									/* For safecrt, this is done already in _safecrt_cfltcvt */

									/* '#' and precision == 0 means force a decimal point */
									if ((flags & FL_ALTERNATE) && precision == 0)
									{
										//TODO _forcdecpt_l(text.sz, _loc_update.GetLocaleT());
									}

									/* 'g' format means crop zero unless '#' given */
									if (ch == 'g' && !(flags & FL_ALTERNATE))
									{
										//TODO _cropzeros_l(text.sz, _loc_update.GetLocaleT());
									}

									/* check if result was negative, save '-' for later */
									/* and point to positive part (this is for '0' padding) */
									if (*text.sz == '-') {
										flags |= FL_NEGATIVE;
										++text.sz;
									}
									textlen = (int)strlen(text.sz);     /* compute length of text */
								}
								break;


							case 'd':
							case 'i':
								/* signed decimal output */
								flags |= FL_SIGNED;
								radix = 10;
								goto COMMON_INT;

							case 'u':
								radix = 10;
								goto COMMON_INT;
							case 'p':
								/* write a pointer -- this is like an integer or long */
								/* except we force precision to pad with zeros and */
								/* output in big hex. */

								precision = 2 * sizeof(void *);     /* number of hex digits needed */
#if PTR_IS_INT64
								flags |= FL_I64;                    /* assume we're converting an int64 */
#elif !PTR_IS_INT
								flags |= FL_LONG;                   /* assume we're converting a long */
#endif  /* !PTR_IS_INT */
								/* DROP THROUGH to hex formatting */

							case 'X':
								/* unsigned upper hex output */
								hexadd = 'A' - '9' - 1;     /* set hexadd for uppercase hex */
								goto COMMON_HEX;

							case 'x':
								/* unsigned lower hex output */
								hexadd = 'a' - '9' - 1;     /* set hexadd for lowercase hex */
								/* DROP THROUGH TO COMMON_HEX */

COMMON_HEX:
								radix = 16;
								if (flags & FL_ALTERNATE) {
									/* alternate form means '0x' prefix */
									prefix[0] = '0';
									prefix[1] = (char)('x' - 'a' + '9' + 1 + hexadd);  /* 'x' or 'X' */
									prefixlen = 2;
								}
								goto COMMON_INT;



							case 'o':
								/* unsigned octal output */
								radix = 8;
								if (flags & FL_ALTERNATE) {
									/* alternate form means force a leading 0 */
									flags |= FL_FORCEOCTAL;
								}
								/* DROP THROUGH to COMMON_INT */

COMMON_INT:                     {
									/* This is the general integer formatting routine. */
									/* Basically, we get an argument, make it positive */
									/* if necessary, and convert it according to the */
									/* correct radix, setting text and textlen */
									/* appropriately. */

#if _INTEGRAL_MAX_BITS >= 64
									unsigned __int64 number;    /* number to convert */
									int digit;              /* ascii value of digit */
									__int64 l;              /* temp long value */
#else  /* _INTEGRAL_MAX_BITS >= 64        */
									unsigned long number;   /* number to convert */
									int digit;              /* ascii value of digit */
									long l;                 /* temp long value */
#endif  /* _INTEGRAL_MAX_BITS >= 64        */

									/* 1. read argument into l, sign extend as needed */
#if _INTEGRAL_MAX_BITS >= 64
									if (flags & FL_I64)
										l = get_arg<__int64>(&pargs);
									else
#endif  /* _INTEGRAL_MAX_BITS >= 64        */

										if (flags & FL_LONGLONG)

											l = get_arg<long long>(&pargs);
										else
#if !LONG_IS_INT
											if (flags & FL_LONG)
												l = get_arg<long>(&pargs);
											else
#endif  /* !LONG_IS_INT */

#if !SHORT_IS_INT
												if (flags & FL_SHORT) {
													if (flags & FL_SIGNED)
														l = (short) get_arg<int>(&pargs); /* sign extend */
													else

														l = (unsigned short) get_arg<int>(&pargs);    /* zero-extend*/

												} else
#endif  /* !SHORT_IS_INT */
												{
													if (flags & FL_SIGNED)

														l = get_arg<int>(&pargs); /* sign extend */

													else

														l = (unsigned int) get_arg<int>(&pargs);    /* zero-extend*/

												}


												/* 2. check for negative; copy into number */
												if ( (flags & FL_SIGNED) && l < 0) {
													number = -l;
													flags |= FL_NEGATIVE;   /* remember negative sign */
												} else {
													number = l;
												}

#if _INTEGRAL_MAX_BITS >= 64
												if ( (flags & FL_I64) == 0 && (flags & FL_LONGLONG) == 0 ) {
													/*
													 * Unless printing a full 64-bit value, insure values
													 * here are not in cananical longword format to prevent
													 * the sign extended upper 32-bits from being printed.
													 */
													number &= 0xffffffff;
												}
#endif  /* _INTEGRAL_MAX_BITS >= 64        */

												/* 3. check precision value for default; non-default */
												/*    turns off 0 flag, according to ANSI. */
												if (precision < 0)
													precision = 1;  /* default precision */
												else {
													flags &= ~FL_LEADZERO;
													if (precision > MAXPRECISION)
														precision = MAXPRECISION;
												}

												/* 4. Check if data is 0; if so, turn off hex prefix */
												if (number == 0)
													prefixlen = 0;

												/* 5. Convert data to ASCII -- note if precision is zero */
												/*    and number is zero, we get no digits at all.       */

												text.sz = &buffer.sz[BUFFERSIZE-1];    /* last digit at end of buffer */

												while (precision-- > 0 || number != 0) {
													digit = (int)(number % radix) + '0';
													number /= radix;                /* reduce number */
													if (digit > '9') {
														/* a hex digit, make it a letter */
														digit += hexadd;
													}
													*text.sz-- = (char)digit;       /* store the digit */
												}

												textlen = (int)((char *)&buffer.sz[BUFFERSIZE-1] - text.sz); /* compute length of number */
												++text.sz;          /* text points to first digit now */



												/* 6. Force a leading zero if FORCEOCTAL flag set */
												if ((flags & FL_FORCEOCTAL) && (textlen == 0 || text.sz[0] != '0')) {
													*--text.sz = '0';
													++textlen;      /* add a zero */
												}
								}
								break;
						}

						/* At this point, we have done the specific conversion, and */
						/* 'text' points to text to print; 'textlen' is length.  Now we */
						/* justify it, put on prefixes, leading zeros, and then */
						/* print it. */

						if (!no_output)
						{
							int padding;    /* amount of padding, negative means zero */

							if (flags & FL_SIGNED) {
								if (flags & FL_NEGATIVE) {
									/* prefix is a '-' */
									prefix[0] = '-';
									prefixlen = 1;
								}
								else if (flags & FL_SIGN) {
									/* prefix is '+' */
									prefix[0] = '+';
									prefixlen = 1;
								}
								else if (flags & FL_SIGNSP) {
									/* prefix is ' ' */
									prefix[0] = ' ';
									prefixlen = 1;
								}
							}

							/* calculate amount of padding -- might be negative, */
							/* but this will just mean zero */
							padding = fldwidth - textlen - prefixlen;

							/* put out the padding, prefix, and text, in the correct order */

							if (!(flags & (FL_LEFT | FL_LEADZERO))) {
								/* pad on left with blanks */
								obuf.write_multi_char(' ', padding, charsout);
							}

							/* write prefix */
							obuf.write_string(prefix, prefixlen, charsout);

							if ((flags & FL_LEADZERO) && !(flags & FL_LEFT)) {
								/* write leading zeros */
								obuf.write_multi_char('0', padding, charsout);
							}

							/* write text */

							if (bufferiswide && (textlen > 0)) {
								wchar_t *p = text.wz;
								int retval;
								char L_buffer[MB_LEN_MAX+1];
								int count = textlen;
								while (count--) {
#ifdef WIN32 
									errno_t e = wctomb_s(&retval, L_buffer, sizeof(L_buffer), *p++);
									if (e != 0 || retval == 0) 
#else 
									retval = wctomb(L_buffer,*p++); 
									if (retval > 0)
#endif //
									{
										charsout = -1;
										break;
									}

									obuf.write_string(L_buffer, retval, charsout);
								}
							} else {
								obuf.write_string(text.sz, textlen, charsout);
							}

							if (charsout >= 0 && (flags & FL_LEFT)) {
								/* pad on right with blanks */
								obuf.write_multi_char(' ', padding, charsout);
							}

							/* we're done! */
						}
						if (heapbuf) {
							free(heapbuf);
							heapbuf = NULL;
						}
						break;
					}
				}
						
		}//switch(chstate)

	}//while 

	return charsout;        /* return value = number of characters written */
}

#endif // 

